package PWS;

/*************************************************************************
 *  Compilation:  javac RSA.java
 *  Execution:    java RSA N
 *  
 *  Generate an N-bit public and private RSA key and use to encrypt
 *  and decrypt a random message.
 * 
 *  % java RSA 50
 *  public  = 65537
 *  private = 553699199426609
 *  modulus = 825641896390631
 *  message   = 48194775244950
 *  encrpyted = 321340212160104
 *  decrypted = 48194775244950
 *
 *  Known bugs (not addressed for simplicity)
 *  -----------------------------------------
 *  - It could be the case that the message >= modulus. To avoid, use
 *    a do-while loop to generate key until modulus happen to be exactly N bits.
 *
 *  - It's possible that gcd(phi, publicKey) != 1 in which case
 *    the key generation fails. This will only happen if phi is a
 *    multiple of 65537. To avoid, use a do-while loop to generate
 *    keys until the gcd is 1.
 *
 *************************************************************************/
import java.math.BigInteger;
import java.security.SecureRandom;

public class RSA {

    private final static SecureRandom secureRandom = new SecureRandom();
    private BigInteger privateKey;
    private BigInteger publicKey;
    private BigInteger mod;

    RSA(String s) {
        int N = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            int j = (int) c;
            N += j;
        }
        BigInteger p = BigInteger.probablePrime(N / 2, secureRandom);
        BigInteger q = BigInteger.probablePrime(N / 2, secureRandom);
        BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));

        mod = p.multiply(q);
        publicKey = new BigInteger("618749"); // common value = 2^16 + 1
        while (phi.gcd(publicKey) != BigInteger.ONE) {
            privateKey = publicKey.modInverse(phi);
        }
    }

    RSA(int N) {
        BigInteger p = BigInteger.probablePrime(N / 2, secureRandom);
        BigInteger q = BigInteger.probablePrime(N / 2, secureRandom);
        BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));

        mod = p.multiply(q);
        publicKey = new BigInteger("618749"); // common value = 2^16 + 1

        while (phi.gcd(publicKey) != BigInteger.ONE) {
            privateKey = publicKey.modInverse(phi);
        }


    }

    BigInteger encrypt(String message) {
        byte[] bytes = message.getBytes();
        BigInteger bi = new BigInteger(bytes);
        return bi.modPow(publicKey, mod);
    }

    String decrypt(BigInteger encrypted) {
        byte[] bytes = encrypted.modPow(privateKey, mod).toByteArray();
        String out = new String(bytes);
        return out;
    }

    @Override
    public String toString() {
        String s = "";
        s += "Public key  = " + publicKey + "\n";
        s += "Private key = " + privateKey + "\n";
        s += "Modulus = " + mod;
        return s;
    }
}